package ui;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.lang.management.MemoryPoolMXBean;

import javax.swing.JPanel;

public class Surface extends JPanel implements Runnable {
	private final MemoryMonitorPanel monitor;
	public Thread thread;
	public long sleepAmount = 1000;
	public int usageHistCount = 20000;
	private int w, h;
	private BufferedImage image;
	private Graphics2D g2d;
	private Font font = new Font("Times New Roman", Font.PLAIN, 11);
	private int columnInc;
	private float usedMem[][];
	private int ptNum[];
	private int ascent, descent;
	private Rectangle graphOutlineRect = new Rectangle();
	private Rectangle2D mfRect = new Rectangle2D.Float();
	private Rectangle2D muRect = new Rectangle2D.Float();
	private Line2D graphLine = new Line2D.Float();
	private Color graphColor = new Color(46, 139, 87);
	private Color mfColor = new Color(0, 100, 0);
	private String usedStr;

	public Surface(MemoryMonitorPanel monitor) {
		super();
		this.monitor = monitor;
		this.monitor.setBackground(Color.black);
		usedMem = new float[MemoryMonitorPanel.poolSize][];
		ptNum = new int[MemoryMonitorPanel.poolSize];
	}

	public Dimension getMinimumSize() {
		return getPreferredSize();
	}

	public Dimension getMaximumSize() {
		return getPreferredSize();
	}

	public Dimension getPreferredSize() {
		return new Dimension(135, 80);
	}

	protected void paintComponent(Graphics g) {
		if (g2d == null)
			return;

		g2d.setBackground(this.monitor.getBackground());
		g2d.clearRect(0, 0, w, h);

		h = h / ((MemoryMonitorPanel.poolSize + MemoryMonitorPanel.poolSize % 2) / 2);
		w = w / 2;

		int k = 0; // index of memory pool.
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < (MemoryMonitorPanel.poolSize + MemoryMonitorPanel.poolSize % 2) / 2; j++) {
				plotMemoryUsage(w * i, h * j, w, h, k);
				if (++k >= MemoryMonitorPanel.poolSize) {
					i = 3;
					j = (MemoryMonitorPanel.poolSize + MemoryMonitorPanel.poolSize % 2) / 2;
					break;
				}
			}
		}
		g.drawImage(image, 0, 0, this);
	}

	public void plotMemoryUsage(int x1, int y1, int x2, int y2, int npool) {

		MemoryPoolMXBean mp = MemoryMonitorPanel.pool.get(npool);
		float usedMemory = mp.getUsage().getUsed();
		float totalMemory = mp.getUsage().getMax();

		// .. Draw allocated and used strings ..
		g2d.setColor(Color.green);

		// Print Max memory allocated for this memory pool.
		g2d.drawString(String.valueOf((int) totalMemory / 1024) + "K Max ", x1 + 4.0f, (float) y1 + ascent + 0.5f);
		g2d.setColor(Color.yellow);

		// Print the memory pool name.
		g2d.drawString(mp.getName(), x1 + x2 / 2, (float) y1 + ascent + 0.5f);

		// Print the memory used by this memory pool.
		usedStr = String.valueOf((int) usedMemory / 1024) + "K used";
		g2d.setColor(Color.green);
		g2d.drawString(usedStr, x1 + 4, y1 + y2 - descent);

		// Calculate remaining size
		float ssH = ascent + descent;
		float remainingHeight = (float) (y2 - (ssH * 2) - 0.5f);
		float blockHeight = remainingHeight / 10;
		float blockWidth = 20.0f;
		float remainingWidth = (float) (x2 - blockWidth - 10);

		// .. Memory Free ..
		g2d.setColor(mfColor);
		int MemUsage = (int) (((totalMemory - usedMemory) / totalMemory) * 10);
		int i = 0;
		for (; i < MemUsage; i++) {
			mfRect.setRect(x1 + 5, (float) y1 + ssH + i * blockHeight, blockWidth, (float) blockHeight - 1);
			g2d.fill(mfRect);
		}

		// .. Memory Used ..
		g2d.setColor(Color.green);
		for (; i < 10; i++) {
			muRect.setRect(x1 + 5, (float) y1 + ssH + i * blockHeight, blockWidth, (float) blockHeight - 1);
			g2d.fill(muRect);
		}

		// .. Draw History Graph ..
		if (remainingWidth <= 30)
			remainingWidth = (float) 30;
		if (remainingHeight <= ssH)
			remainingHeight = (float) ssH;
		g2d.setColor(graphColor);
		int graphX = x1 + 30;
		int graphY = y1 + (int) ssH;
		int graphW = (int) remainingWidth;
		int graphH = (int) remainingHeight;

		graphOutlineRect.setRect(graphX, graphY, graphW, graphH);
		g2d.draw(graphOutlineRect);

		int graphRow = graphH / 10;

		// .. Draw row ..
		for (int j = graphY; j <= graphH + graphY; j += graphRow) {
			graphLine.setLine(graphX, j, graphX + graphW, j);
			g2d.draw(graphLine);
		}

		// .. Draw animated column movement ..
		int graphColumn = graphW / 15;

		if (columnInc == 0) {
			columnInc = graphColumn;
		}

		for (int j = graphX + columnInc; j < graphW + graphX; j += graphColumn) {
			graphLine.setLine(j, graphY, j, graphY + graphH);
			g2d.draw(graphLine);
		}

		--columnInc;

		// Plot memory usage by this memory pool.
		if (usedMem[npool] == null) {
			usedMem[npool] = new float[usageHistCount];
			ptNum[npool] = 0;
		}

		// save memory usage history.
		usedMem[npool][ptNum[npool]] = usedMemory;

		g2d.setColor(Color.yellow);

		int w1; // width of memory usage history.
		if (ptNum[npool] > graphW) {
			w1 = graphW;
		} else {
			w1 = ptNum[npool];
		}

		for (int j = graphX + graphW - w1, k = ptNum[npool] - w1; k < ptNum[npool]; k++, j++) {
			if (k != 0) {
				if (usedMem[npool][k] != usedMem[npool][k - 1]) {
					int h1 = (int) (graphY + graphH * ((totalMemory - usedMem[npool][k - 1]) / totalMemory));
					int h2 = (int) (graphY + graphH * ((totalMemory - usedMem[npool][k]) / totalMemory));
					g2d.drawLine(j - 1, h1, j, h2);
				} else {
					int h1 = (int) (graphY + graphH * ((totalMemory - usedMem[npool][k]) / totalMemory));
					g2d.fillRect(j, h1, 1, 1);
				}
			}
		}
		if (ptNum[npool] + 2 == usedMem[npool].length) {
			// throw out oldest point
			for (int j = 1; j < ptNum[npool]; j++) {
				usedMem[npool][j - 1] = usedMem[npool][j];
			}
			--ptNum[npool];
		} else {
			ptNum[npool]++;
		}
	}

	public void start() {
		thread = new Thread(this);
		thread.setPriority(Thread.MIN_PRIORITY);
		thread.setName("MemoryMonitor");
		thread.start();
	}

	public synchronized void stop() {
		thread = null;
		// this.monitor.notify();
	}

	public void run() {

		Thread me = Thread.currentThread();

		while (thread == me && !this.monitor.isShowing() || this.monitor.getSize().width == 0) {
			try {
				thread.sleep(500);
			} catch (InterruptedException e) {
				return;
			}
		}

		while (thread == me && this.monitor.isShowing()) {
			Dimension d = this.monitor.getSize();
			if (d.width != w || d.height != h) {
				w = d.width;
				h = d.height;
				image = (BufferedImage) this.monitor.createImage(w, h);
				g2d = image.createGraphics();
				g2d.setFont(font);
				FontMetrics fm = g2d.getFontMetrics(font);
				ascent = (int) fm.getAscent();
				descent = (int) fm.getDescent();
			}
			this.monitor.repaint();
			try {
				thread.sleep(sleepAmount);
			} catch (InterruptedException e) {
				break;
			}
		}
		thread = null;
	}
}
